package com.learnyou.server.xingzuo.biz.common;


import com.google.gson.Gson;
import com.learnyou.server.xingzuo.common.exception.LearnyouBootException;
import com.learnyou.server.xingzuo.common.config.WechatConfig;
import com.learnyou.server.xingzuo.common.constants.ErrorCode;
import com.learnyou.server.xingzuo.dto.paramDTO.OrgMpDTO;
import com.learnyou.server.xingzuo.dto.paramDTO.wechat.WechatNormalTemplateMsgParamDTO;
import com.learnyou.server.xingzuo.dto.paramDTO.wechat.WechatUniformTemplateMsgDTO;
import com.learnyou.server.xingzuo.dto.paramDTO.wechat.template.AttendanceMsgDataDTO;
import com.learnyou.server.xingzuo.dto.paramDTO.wechat.template.MiniProgramObject;
import com.learnyou.server.xingzuo.dto.paramDTO.wechat.template.NormalTemplateMsgDataDTO;
import com.learnyou.server.xingzuo.dto.paramDTO.wechat.template.WechatMPTemplateMsgDTO;
import com.learnyou.server.xingzuo.dto.resultDTO.wechat.WechatNormalResultDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public abstract class AbstractWechatClientBiz implements WechatClientBiz {

    public String getTokenOf(CacheTokenEnum tokenType, String appId) {
        ReentrantLock lock = new ReentrantLock();
        try {
            Object tokenDataDTO = getTokenFromCache(tokenType, appId);
            if (!isTokenValid(tokenType, tokenDataDTO)) {
                // token 无效，需要更新
                boolean gotLock = lock.tryLock();
                boolean needUpdate = true;

                // 尝试获得锁
                while (!gotLock) {
                    // 没有获得锁的时候，
                    // 每次都看一下缓存的token是否已经可用
                    tokenDataDTO = getTokenFromCache(tokenType, appId);
                    if (isTokenValid(tokenType, tokenDataDTO)) {
                        needUpdate = false;
                        break;
                    } else {
                        // 直到获取到锁
                        gotLock = lock.tryLock();
                    }
                }

                if (needUpdate) {
                    // 刷新token
                    tokenDataDTO = refreshToken(tokenType, tokenDataDTO);

                    Method errcodeMethod = tokenType.getClazz().getMethod("getErrcode");
                    Object errcode = errcodeMethod.invoke(tokenDataDTO);
                    if (Objects.nonNull(tokenDataDTO) && (ObjectUtils.isEmpty(errcode) || errcode.toString().equals("0"))) {
                        String key = tokenType == CacheTokenEnum.THIRD_AUTHORIZER_TOKEN?  appId: tokenType.getCacheKey();
                        // 更新缓存
                        this.getCache().put(key, tokenDataDTO);
                        // 更新storage
                        this.upsertStorage(key, tokenDataDTO);
                    } else {
                        throw new LearnyouBootException(ErrorCode.WECHAT_TOKEN_FAILED);
                    }
                }
            }

            Object errorCode = tokenType.getClazz().getMethod("getErrcode").invoke(tokenDataDTO);

            if (Objects.nonNull(tokenDataDTO) && (ObjectUtils.isEmpty(errorCode) || errorCode.equals("0"))) {
                String tokenMethodName = tokenType.getTokenMethodName();
                Method tokenMethod = tokenType.getClazz().getMethod(tokenMethodName);
                return tokenMethod.invoke(tokenDataDTO).toString();
            } else {
                throw new LearnyouBootException(ErrorCode.WECHAT_TOKEN_FAILED);
            }
        } catch (Exception ex) {
            log.warn("error:", ex);
            throw new LearnyouBootException(ErrorCode.WECHAT_TOKEN_FAILED);
        }finally {
            if (lock.isLocked()) {
                lock.unlock();
            }
        }
    }

    public Object getTokenFromCache(CacheTokenEnum token, String appId) {
        String key = token == CacheTokenEnum.THIRD_AUTHORIZER_TOKEN?  appId: token.getCacheKey();
        Object obj = this.getCache().get(key, token.getClazz());
        if (Objects.isNull(obj)) {
            String storageValue = getFromStorage(key);
            if (Objects.nonNull(storageValue)) {
                obj = new Gson().fromJson(storageValue, token.getClazz());
            }
        }
        return obj;
    }

    private boolean isTokenValid(CacheTokenEnum token, Object obj) {
        boolean isValid = false;
        Long expiredGap = 60l; // 超时冗余（秒）

        try {
            Method createAtMethod = token.getClazz().getMethod("getCreated_at");
            Method expiredInMethod = token.getClazz().getMethod("getExpires_in");

            if (!Objects.isNull(obj)) {
                Object createdAtObj = createAtMethod.invoke(obj);
                if (Objects.nonNull(createdAtObj)) {
                    Long createdAt = Long.valueOf(String.valueOf(createdAtObj));
                    Long expiredIn = (Long) expiredInMethod.invoke(obj);
                    if ((createdAt + expiredIn - expiredGap) > (System.currentTimeMillis() / 1000)) {
                        isValid = true;
                    }
                }
            }
            return isValid;
        } catch (Exception ex) {
            return false;
        }
    }

    public void updateTicketStorage(String newTicket) {
        this.upsertStorage(XingzuoConstants.CACHE_TICKET_KEY, newTicket);
    }

    public abstract Cache getCache();

    public abstract Object refreshToken(CacheTokenEnum tokenEnum, Object oldValue);

    public abstract String getFromStorage(String key);

    public abstract int upsertStorage(String key, Object value);
}
